using System;
using System.Collections.Generic;
using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.Ast;

namespace NTrace
{
    public class FileProcessor
    {
        private Environment environment;
        private CompilationUnit parsedCode;

        public FileProcessor(Environment environment)
        {
            this.environment = environment;
        }

        public String Process(String codeFile)
        {
            SupportedLanguage language;
            String fileExtension = System.IO.Path.GetExtension(codeFile);
            if (String.Compare(".cs", fileExtension, true) == 0)
            {
                language = SupportedLanguage.CSharp;
            }
            else
            {
                throw new PreprocessorException(PreprocessorErrors.InvalidFileExtension, String.Format("Invalid file extension '{0}'.", fileExtension)) { File = codeFile };
            }

            this.EnsureOutputFolderExists();

            // Get the cached file name
            String outputFile = FileProcessor.GetCachedFileName(this.environment.OutputFolder, codeFile);

            // If the file already exists on-disk, we'll treat it as a cached item and will not re-generate anything
            if (!System.IO.File.Exists(outputFile))
            {
                PreprocessorContext context = new PreprocessorContext(this.environment, codeFile, language);

                // Since we want to preserve the original formatting from the
                // source, we'll copy the contents of the file and have our
                // processor munge it a bit to generate the trace calls
                using (System.IO.StreamReader reader = new System.IO.StreamReader(codeFile))
                {
                    for (String line = reader.ReadLine(); line != null; line = reader.ReadLine())
                    {
                        context.Lines.Add(line);
                    }
                }

                using (IParser parser = ParserFactory.CreateParser(codeFile))
                {
                    // We want the contents of the methods parsed as well
                    parser.ParseMethodBodies = true;
                    parser.Parse();

                    this.parsedCode = parser.CompilationUnit;

                    NTraceAstVisitor ntraceVisitor = new NTraceAstVisitor(context);
                    this.parsedCode.AcceptVisitor(ntraceVisitor, null);

                    this.WriteProcessedFile(context, outputFile);
                    context.TraceMessageFile.Write();
                }
            }

            return outputFile;
        }

        /// <summary>
        /// Ensure our preprocessor's output folder exists
        /// </summary>
        private void EnsureOutputFolderExists()
        {
            if (!System.IO.Directory.Exists(this.environment.OutputFolder))
            {
                System.IO.Directory.CreateDirectory(this.environment.OutputFolder);
            }
        }

        private void WriteProcessedFile(PreprocessorContext context, String outputFile)
        {
            ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer = context.Language.CreatePrinter();

            // Use the printer to generate a comment header stating this is an
            // auto-generated file 
            this.AddHeader(printer);

            using (System.IO.TextWriter writer = new System.IO.StreamWriter(outputFile, false))
            {
                writer.Write(printer.Text);

                // Emit our munged source, wrapping it with processing directives that map the new lines
                // to the original source file
                using (OriginalSourceWrapper wrapper = context.Language.CreateSourceWrapper(context.CodeFile, writer))
                {
                    foreach (String line in context.Lines)
                    {
                        writer.WriteLine(line);
                    }
                }
            }
        }

        private void AddHeader(ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer)
        {
            System.Reflection.AssemblyName currentAssemblyName = System.Reflection.Assembly.GetExecutingAssembly().GetName();

            this.PrintComment(printer, String.Empty);
            this.PrintComment(printer, "Warning: this code was auto-generated by a tool.");
            this.PrintComment(printer, "Do not edit this file, as your changes will be overwritten.");
            this.PrintComment(printer, String.Empty);
            this.PrintComment(printer, String.Format("{0}, Version {1}", currentAssemblyName.Name, currentAssemblyName.Version));
            this.PrintComment(printer, String.Empty);
        }

        private void PrintComment(ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer, String text)
        {
            Comment comment = new Comment(
              CommentType.SingleLine,
              text,
              true,
              Location.Empty,
              Location.Empty);
            printer.OutputFormatter.PrintComment(comment, false);
        }

        public static String GetCachedFileName(String outputFolder, String inputFile)
        {
            String baseFileName = System.IO.Path.GetFileNameWithoutExtension(inputFile);
            String extension = System.IO.Path.GetExtension(inputFile);
            String output = System.IO.Path.Combine(
              outputFolder,
              baseFileName + extension);

            CRC32 checksumGenerator = new CRC32();
            Byte[] hash;
            using (System.IO.Stream fileStream = new System.IO.FileStream(inputFile, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read))
            {
                hash = checksumGenerator.ComputeHash(fileStream);
            }

            System.Text.StringBuilder builder = new System.Text.StringBuilder();
            builder.Append(System.IO.Path.Combine(
              outputFolder,
              baseFileName));
            builder.Append("_");
            for (int byteIndex = 0; byteIndex < hash.Length; byteIndex++)
            {
                builder.AppendFormat("{0:X2}", hash[byteIndex]);
            }
            builder.Append(extension);

            output = builder.ToString();
            return output;
        }
    }
}
